import axios, {
  AxiosError,
  AxiosRequestConfig,
  AxiosResponse,
  CancelTokenSource,
  InternalAxiosRequestConfig
} from 'axios';
import dayjs from 'dayjs';
import { camelCase, get } from 'lodash-es';

import { ROUTE_MAP } from '@/application/constants/routerMap';
import Storage from '@/application/utils/storage';
import { Loading } from '@/components/Loading';

import { cleanParams, formatObjectKeys } from '../common';
import Catcher from './errCatcher';
import { RequestConfig, RequestOptions } from './type';

declare module 'axios' {
  export interface AxiosRequestConfig {
    // 此参数为true时请求不带上token
    noToken?: boolean;
    // 此参数为true时请求不对返回值进行校验
    noValidate?: boolean;
    noGlobalMessage?: boolean;
    hideLoading?: boolean;
    requestTime?: number;
  }
}

export type StandardResponse<T = unknown> = {
  code?: number;
  success?: boolean;
  message?: string;
  info?: string;
  result: T;
};

function stringify(value: unknown) {
  return `${JSON.stringify(value)} \n`;
}

const axiosInstance = axios.create({
  baseURL: `${__BASE_URL__}`
});

/**
 * 输出request 参数
 * @param request
 */
const logRequest = (request: RequestConfig) => {
  console.info('\n');
  console.info(
    `[Network Request]：${request.url} ${dayjs().format('YYYY-MM-DD HH:mm:ss')} \n`,
    stringify(request.headers),
    stringify(request.data),
    stringify(request.params)
  );
};

/**
 * 输出response参数
 * @param response
 * @param timing
 */
const logResponse = (response: AxiosResponse, timing: number) => {
  console.log('\n');
  console.log(
    `[Network Response]：${response.config.url} ${Math.floor(timing)}ms \n`,
    stringify(response.data)
  );
};
/**
 * 异常输出
 * @param error
 */
const logError = (error: any) => {
  console.log('\n');

  if (axios.isCancel(error) || axios.isAxiosError(error)) {
    console.log(`[Network Error]：${error.message}\n`);
  } else {
    console.log(
      `[Network Error]：${error.config.url}\n`,
      stringify(get(error, 'data', get(error, 'response.data', { errMsg: '未知错误' })))
    );
  }
};

export const cancelTokenList: CancelTokenSource[] = [];

// 请求拦截器
axiosInstance.interceptors.request.use(
  async (config: InternalAxiosRequestConfig & RequestConfig) => {
    const user = Storage.login.get();
    const _config = config;
    _config.requestTime = Date.now();

    // logRequest(_config);

    // 全局CancelToken
    const source = axios.CancelToken.source();
    cancelTokenList.push(source);
    _config.cancelToken = source.token;

    try {
      const { hideLoading } = config;
      if (!hideLoading) Loading.show();
    } catch (e) {
      logError(e);
      return Promise.reject(e);
    }
    return {
      ..._config,
      headers: Object.assign({}, config.headers, {
        token: user?.token || ''
      })
    };
  }
);

// 响应拦截器
axiosInstance.interceptors.response.use(
  (res: AxiosResponse<StandardResponse<any>>) => {
    const { config } = res;

    // 输出日志
    // const requestTime = res.config?.requestTime || 0;
    // const responseTime = Date.now();
    // const timing = responseTime - requestTime;

    // logResponse(res, timing);

    const { hideLoading } = res.config;

    // 清空并处理全局CancelToken List
    cancelTokenList.length = 0;
    cancelTokenList.push(...cancelTokenList.filter((x) => x.token !== config.cancelToken));

    // 各种返回状况处理
    const response = res.data as unknown as StandardResponse;

    if (!hideLoading) Loading.hide();

    if (!config.noValidate && response.code !== 200 && response.success !== true) {
      console.log('----------error ');
      Catcher(res, false, config.noGlobalMessage);
    }
    return res;
  },
  (error: AxiosError<StandardResponse, AxiosRequestConfig>) => {
    const requestError = error.response;
    logError(error);
    if (!requestError) {
      Loading.hide();
      return Promise.reject();
    }
    const { hideToast = false, hideLoading = false } = error.config as RequestConfig;
    if (!hideLoading) Loading.hide();

    if (error.response && !hideToast) {
      Catcher(error.response, true, error.config!.noGlobalMessage);
    }

    if (requestError.status === 401) {
      window.location.replace(import.meta.env.BASE_URL + ROUTE_MAP.HOME);
    }

    return Promise.reject(requestError);
  }
);
const MATCH_PATH_PARAMS = /:(\w+)/g;

export class Api {
  get<T>(url: string, data?: AnyObject, config?: RequestConfig) {
    return this.request<T>({ url, method: 'GET', data, config });
  }

  post<T>(url: string, data?: AnyObject, config?: RequestConfig) {
    return this.request<T>({ url, method: 'POST', data, config });
  }

  put<T>(url: string, data?: AnyObject, config?: RequestConfig) {
    return this.request<T>({ url, method: 'PUT', data, config });
  }

  delete<T>(url: string, data?: AnyObject, config?: RequestConfig) {
    return this.request<T>({ url, method: 'DELETE', data, config });
  }

  request<T>(options: RequestOptions<any>) {
    const config = options.config || ({} as RequestConfig);
    const { ignoreCleanParams, headers, ...otherConfig } = config;
    const matchParams = options.url.match(MATCH_PATH_PARAMS);
    let { url } = options;

    let params = cleanParams(options.data) as object;

    if (ignoreCleanParams) {
      params = options.data as object;
    }

    if (matchParams) {
      matchParams.forEach((match) => {
        const key = match.replace(':', '');
        if (Reflect.has(params, key)) {
          url = url.replace(match, Reflect.get(params, key));
          Reflect.deleteProperty(params, key);
        }
      });
    }
    params = formatObjectKeys(params, camelCase) as AnyObject;
    const requestConfig: AxiosRequestConfig = {
      url,
      method: options.method,
      headers,
      ...otherConfig
    };

    if (options.method === 'GET') {
      requestConfig.params = params;
    } else {
      requestConfig.data = params;
    }

    return new Promise<T>((resolve, reject) => {
      axiosInstance
        .request(requestConfig)
        .then((response) => {
          resolve(response.data);
        })
        .catch((e) => {
          reject(e);
        });
    });
  }
}

export default new Api();
